#include "CvGameCoreDLL.h"	// must be first as it's 'the' compiled header for the project
#include <direct.h>
#include <share.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <wchar.h>

#include "CvGlobals.h"
#include "CvGameAI.h"
#include "CvString.h"

#include "NCSaver.h"

#define ZLIB_LEVEL Z_DEFAULT_COMPRESSION
#define ZLIB_CHUNK 32768

#define NCSAVER_FOLDER "My Games\\Warlords\\Saves\\NCSaver"
#define NCSAVER_NAMEPATTERN "My Games\\Warlords\\Saves\\NCSaver\\NCSaver%dXXXXXX"



NCSaver::NCSaver(FDataStreamBase* pOrigStream)
{
	m_pMainStream = pOrigStream;
	init();
}

NCSaver::~NCSaver()
{
	CvString szLogString;

	szLogString.Format("NCSaver::~NCSaver called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	uninit(true);
}

void NCSaver::Release()
{
	uninit(false);

}

void NCSaver::uninit(bool bFinal)
{
	close();

	if(bFinal)
	{
		SAFE_DELETE(m_szPathName);
		SAFE_DELETE(m_pbInBuf);
		SAFE_DELETE(m_pbOutBuf);
	}

}

void NCSaver::init()
{
	m_fStream = NULL;
 	m_szPathName = new char[_MAX_PATH];
	
	m_iBufNdx = -1;
	m_iBufEnd = -1;

	m_pbInBuf = NULL;
	m_pbOutBuf = NULL;

	m_iZlibStatus = Z_OK;
	m_bHasFile = false;
	m_bStarted = false;
}

// Public Methods

void NCSaver::Rewind()
{
	CvString szLogString;

	szLogString.Format("NCSaver::Rewind called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

bool NCSaver::AtEnd()
{
	CvString szLogString;

	szLogString.Format("NCSaver::AtEnd called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return false;
}

void NCSaver::FastFwd()
{
	CvString szLogString;

	szLogString.Format("NCSaver::FastFwd called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

unsigned int  NCSaver::GetPosition() const
{
	CvString szLogString;

	szLogString.Format("NCSaver::GetPosition called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return 0;
}

void NCSaver::SetPosition(unsigned int position)
{
	CvString szLogString;

	szLogString.Format("NCSaver::SetPosition called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Truncate()
{
	CvString szLogString;

	szLogString.Format("NCSaver::Truncate called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Flush()
{
	CvString szLogString;

	szLogString.Format("NCSaver::Flush called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

unsigned int NCSaver::GetEOF() const
{
	CvString szLogString;

	szLogString.Format("NCSaver::GetEOF called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return false;
}

unsigned int NCSaver::GetSizeLeft() const
{
	CvString szLogString;

	szLogString.Format("NCSaver::GetSizeLeft called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return 0;
}

void NCSaver::CopyToMem(void* mem)
{
	CvString szLogString;

	szLogString.Format("NCSaver::CopyToMem called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

unsigned int NCSaver::ReadString(char *szName)
{
	CvString szLogString;

	szLogString.Format("NCSaver::ReadString (char) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return 0;
}

unsigned int NCSaver::ReadString(wchar *szName)
{
	CvString szLogString;

	szLogString.Format("NCSaver::ReadString (std::wchar array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

	return 0;
}


unsigned int NCSaver::ReadString(std::string& szName)
{
	char *readPtr = ReadString();

	szName = readPtr != NULL ? readPtr : "";
	SAFE_DELETE(readPtr);

	return szName.length();
}


unsigned int NCSaver::ReadString(std::wstring& szName)
{
	wchar *readPtr = ReadWideString();

	szName = readPtr != NULL ? readPtr : (const wchar *) "";
	SAFE_DELETE(readPtr);

	return szName.length();
}


unsigned int NCSaver::ReadString(int count, std::string values[])
{
	for(int i = 0;i < count;i++)
	{
		ReadString(values[i]);
	}

	return i - 1;
}


unsigned int NCSaver::ReadString(int count, std::wstring values[])
{
	for(int i = 0;i < count;i++)
	{
		ReadString(values[i]);
	}

	return i - 1;
}



char * NCSaver::ReadString()
{
	char *readPtr = NULL;
	int readCount;

	read(&readCount, sizeof readCount, 1);

	if(readCount >= 0)
	{
		if(readCount > 0)
		{
			readPtr = new char[readCount + 1];
			read(readPtr, sizeof readPtr[0], readCount);
		}
		else
		{
			readPtr = new char;
		}
		readPtr[readCount] = 0;
	}


	return readPtr;
}

wchar *	NCSaver::ReadWideString()
{
	wchar *readPtr = NULL;
	int readCount;

	read(&readCount, sizeof readCount, 1);

	if(readCount >= 0)
	{
		if(readCount > 0)
		{
			readPtr = new wchar[readCount + 1];
			read(readPtr, sizeof readPtr[0], readCount);
		}
		else
		{
			readPtr = new wchar;
		}

		readPtr[readCount] = 0;
	}


	return readPtr;
}

unsigned int NCSaver::WriteString(const wchar *szName)
{
	int count = (szName ? wcslen(szName) : -1);
	Write(count);

	if(count > 0)
	{
		Write(count, szName);
	}
	return count;
}

unsigned int NCSaver::WriteString(const char *szName)
{
	int count = (szName ? strlen(szName) : -1);
	Write(count);

	if(count > 0)
	{
		Write(count, szName);
	}
	return count;
}

unsigned int NCSaver::WriteString(const std::string& szName)
{
	return WriteString(szName.c_str());
}

unsigned int NCSaver::WriteString(const std::wstring& szName)
{
	return WriteString(szName.c_str());
}

unsigned int NCSaver::WriteString(int count, std::string values[])
{
	for(int i = 0;i < count;i++)
	{
		WriteString(values[i]);
	}

	return i - 1;
}

unsigned int NCSaver::WriteString(int count, std::wstring values[])
{
	for(int i = 0;i < count;i++)
	{
		WriteString(values[i]);
	}

	return i - 1;
}

void NCSaver::Read(char *value)
{
	read(value, sizeof value[0], 1);
}

void NCSaver::Read(byte *value)
{
	read(value, sizeof value[0], 1);
}

void NCSaver::Read(int count, char values[])
{
	read(values, sizeof values[0], count);
}

void NCSaver::Read(int count, byte values[])
{
	read(values, sizeof values[0], count);
}

void NCSaver::Read(bool *value)
{
	read(value, sizeof value[0], 1);
}

void NCSaver::Read(int count, bool values[])
{
	read(values, sizeof values[0], count);
}
void NCSaver::Read(short	*s)
{
	read(s, sizeof s[0], 1);
}

void NCSaver::Read(unsigned short	*s)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (ushort) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(int count, short values[])
{
	read(values, sizeof values[0], count);
}

void NCSaver::Read(int count, unsigned short values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (ushort array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(int* i)
{
	read(i, sizeof i[0], 1);
}

void NCSaver::Read(unsigned int* i)
{
	read(i, sizeof i[0], 1);
}

void NCSaver::Read(int count, int values[])
{
	read(values, sizeof values[0], count);
}

void NCSaver::Read(int count, unsigned int values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (uint array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(long* l)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (long) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(unsigned long* l)
{
	read(l, sizeof l[0], 1);
}

void NCSaver::Read(int count, long values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (long array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(int count, unsigned long values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (ulong array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(float* value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (float) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(int count, float values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (float array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(double* value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (double) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Read(int count, double values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Read (double array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write( char value)
{
	write(&value, sizeof value, 1);
}

void NCSaver::Write(byte value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (byte) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}
void NCSaver::Write(int count, const  char values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(int count, const  byte values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(bool value)
{
	write(&value, sizeof value, 1);
}

void NCSaver::Write(int count, const bool values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(short value)
{
	write(&value, sizeof value, 1);
}
void NCSaver::Write(unsigned short value)
{
	write(&value, sizeof value, 1);
}
void NCSaver::Write(int count, const short values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(int count, const unsigned short values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(int value)
{
	write(&value, sizeof value, 1);
}

void NCSaver::Write(unsigned int value)
{
	write(&value, sizeof value, 1);
}

void NCSaver::Write(int count, const int values[])
{
	write(values, sizeof values[0], count);
}

void NCSaver::Write(int count, const unsigned int values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (uint array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(long value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (long array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(unsigned long  value)
{
	write(&value, sizeof value, 1);
}

void NCSaver::Write(int count, const long values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (long array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(int count, const unsigned long values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (ulong array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(float value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (float) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(int count, const float values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (float array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(double value)
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (double) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Write(int count, const double values[])
{
	CvString szLogString;

	szLogString.Format("NCSaver::Write (double array) called");						// NC
	gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Start(FDataStreamBase *pOrigStream)
{
	close();
	m_bStarted = false;
	m_pMainStream = pOrigStream;
}

// Private Methods
void NCSaver::makeFileName()
{
	char *result;
	CvString szFileName;
	const char* apFolders[] = {"My Games", "Warlords", "Saves", "NCSaver"}; 
	int iCurrFolder;
	int iAccessReply;

	szFileName.Format("NCSaver%dXXXXXX", GC.getGameINLINE().getGameTurnYear());

	if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREATE, NULL, 0, m_szPathName)))
	{
		for(iCurrFolder=0;iCurrFolder < 4;iCurrFolder++)
		{
			PathAppend(m_szPathName, apFolders[iCurrFolder]);

			iAccessReply = _access(m_szPathName, 6);

			if(iAccessReply)
			{
				if(errno == ENOENT)
				{
					_mkdir(m_szPathName);
				}
				else
				{
					// we've not got the required acess (or some other failure occurred)
					// this condition requires the process to be aborted
				}
			}
		}
		PathAppend(m_szPathName, szFileName.GetCString());
		result = _mktemp(m_szPathName);
	}
	m_bHasFile = true;
}

void NCSaver::open(bool bWrite)
{
	close();

	m_bIsWriting = bWrite;

	if(bWrite)
	{
		if(!m_bHasFile)
		{
			makeFileName();
		}

		m_pMainStream->WriteString(m_szPathName);
		m_pMainStream->Flush();
	}
	else
	{
		m_pMainStream->ReadString(m_szPathName);
		m_pMainStream->Read(sizeof m_lStartPos, (byte *) &m_lStartPos);
	}

	m_zStrm.zalloc = Z_NULL;
	m_zStrm.zfree = Z_NULL;
	m_zStrm.opaque = Z_NULL;

	m_iZlibStatus = Z_OK;

	if(bWrite)
	{
		m_fStream = fopen(m_szPathName, "ab");
		fseek(m_fStream, 0L, SEEK_END);
		fgetpos(m_fStream, &m_lStartPos);
		m_pMainStream->Write( sizeof m_lStartPos, (byte *) &m_lStartPos);
		m_pMainStream->Flush();
		m_iZlibStatus = deflateInit(&m_zStrm, ZLIB_LEVEL);
	}
	else
	{
		m_fStream = fopen(m_szPathName, "rb");
		fsetpos(m_fStream, &m_lStartPos);
		m_zStrm.avail_in = 0;
		m_zStrm.next_in = Z_NULL;
		m_iZlibStatus = inflateInit(&m_zStrm);
	}

	if(m_iZlibStatus != Z_OK)
	{
		return;
	}

	if(!m_pbInBuf)
	{
		m_pbInBuf = new byte[ZLIB_CHUNK];
	}

	if(!m_pbOutBuf)
	{
		m_pbOutBuf = new byte[ZLIB_CHUNK];
	}
	else
	{
		m_zStrm.avail_in = 0;
	}

	m_iBufNdx = 0;
	m_iBufEnd = 0;

}

void NCSaver::Deflate()
{

	//CvString szLogString;

	//szLogString.Format("NCSaver::Deflate called\n\tzlib in %d %d out %d %d\n\tbuf ndx %d end %d",
	//						m_zStrm.avail_in, m_zStrm.total_in, m_zStrm.avail_out, m_zStrm.total_out,
	//						m_iBufNdx, m_iBufEnd);						// NC
	//gDLL->logMsg("NCDebug.log", szLogString);									// NC

	m_zStrm.avail_in = m_iBufNdx;
	int iFlushStatus = m_iBufNdx < ZLIB_CHUNK ? Z_FINISH : Z_NO_FLUSH;
	m_zStrm.next_in = m_pbInBuf;

	do
	{
		m_zStrm.avail_out = ZLIB_CHUNK;
		m_zStrm.next_out = m_pbOutBuf;

		m_iZlibStatus = deflate(&m_zStrm, iFlushStatus);

		if(m_iZlibStatus != Z_STREAM_ERROR)
		{
			int iHave = ZLIB_CHUNK - m_zStrm.avail_out;
			int iWritten = fwrite(m_pbOutBuf, 1, iHave, m_fStream);

			if(iWritten != iHave || ferror(m_fStream))
			{
				m_iZlibStatus = Z_ERRNO;
			}
			else
			{
				m_iBufNdx = 0;
			}
		}
	} while(iFlushStatus == Z_FINISH && m_iZlibStatus == Z_OK);
	//	loop again if we tried to flush and didn't get a Z_STREAM_END (but no error)
	//szLogString.Format("NCSaver::Deflate exit\n\tzlib in %d %d out %d %d\n\tbuf ndx %d end %d\n\n",
	//						m_zStrm.avail_in, m_zStrm.total_in, m_zStrm.avail_out, m_zStrm.total_out,
	//						m_iBufNdx, m_iBufEnd);						// NC
	//gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::Inflate()
{

	//CvString szLogString;

	//szLogString.Format("NCSaver::Inflate called\n\tzlib in %d %d out %d %d\n\tbuf ndx %d end %d",
	//						m_zStrm.avail_in, m_zStrm.total_in, m_zStrm.avail_out, m_zStrm.total_out,
	//						m_iBufNdx, m_iBufEnd);						// NC
	//gDLL->logMsg("NCDebug.log", szLogString);									// NC

	if(m_zStrm.avail_in == 0)
	{
		m_zStrm.avail_in = fread(m_pbInBuf, 1, ZLIB_CHUNK, m_fStream);
		m_zStrm.next_in = m_pbInBuf;


		if(ferror(m_fStream))
		{
			m_iZlibStatus = Z_ERRNO;
			return;
		}
	}

	if(m_zStrm.avail_in == 0)
	{							//	put in assert and set status to stream end if this one triggered
		return;
	}

	m_zStrm.avail_out = ZLIB_CHUNK;
	m_zStrm.next_out = m_pbOutBuf;

	m_iZlibStatus = inflate(&m_zStrm, Z_NO_FLUSH);

	if(m_iZlibStatus != Z_STREAM_ERROR)
	{
		switch(m_iZlibStatus)
		{
		case Z_NEED_DICT:
			m_iZlibStatus = Z_DATA_ERROR;

		case Z_DATA_ERROR:
		case Z_MEM_ERROR:
			m_iBufEnd = -1;
			return;
		}
		m_iBufNdx = 0;
		m_iBufEnd = ZLIB_CHUNK - m_zStrm.avail_out;
	}
	//szLogString.Format("NCSaver::Inflate exit\n\tzlib in %d %d out %d %d\n\tbuf ndx %d end %d\n",
	//						m_zStrm.avail_in, m_zStrm.total_in, m_zStrm.avail_out, m_zStrm.total_out,
	//						m_iBufNdx, m_iBufEnd);						// NC
	//gDLL->logMsg("NCDebug.log", szLogString);									// NC

}

void NCSaver::close()
{
	if(m_fStream)
	{

		if(m_bIsWriting)
		{
			Deflate();

			fclose(m_fStream);

			deflateEnd(&m_zStrm);
		}
		else
		{
			inflateEnd(&m_zStrm);
		}

		//CvString szCloseMsg;

		//szCloseMsg.Format("NCSaver::close finished %s - Zlib Status %d", m_bIsWriting ? "writing" : "reading", m_iZlibStatus);
		//gDLL->logMsg("NCDebug.log", szCloseMsg);									// NC
	}

	m_fStream = NULL;
	m_bStarted = false;


}

void NCSaver::write(const void *buffer, size_t size, int count)
{
	byte* bufPtr = (byte *) buffer;

	if(!m_fStream)
	{
		open(true);
	}

	long lMoved = 0;
	long lToMove = size * count;

	while(lMoved < lToMove)
	{
		if(m_iBufNdx < ZLIB_CHUNK)
		{
			m_pbInBuf[m_iBufNdx++] = *bufPtr++;
			lMoved++;
		}
		else
		{
			Deflate();
		}

		if(m_iZlibStatus != Z_OK && m_iZlibStatus != Z_STREAM_END)
		{
			break;
		}
	}
}

void NCSaver::read(void *buffer, size_t size, int count)
{
	byte* bufPtr = (byte *) buffer;

	if(!m_fStream)
	{
		open(false);
	}

	long lMoved = 0;
	long lToMove = size * count;

	while(lMoved < lToMove)
	{
		if(m_iBufNdx < m_iBufEnd)
		{
			*bufPtr++ = m_pbOutBuf[m_iBufNdx++];
			lMoved++;
		}
		else
		{
			Inflate();
		}

		if(m_iZlibStatus != Z_OK && m_iZlibStatus != Z_STREAM_END)
		{
			break;
		}

	}
}

